/**
 * @file bsp_timer.c
 * @author your name (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2023-03-30
 *
 * @copyright Copyright (c) 2023
 *
 */

#include "bsp.h"

/* 定于软件定时器结构体变量 */
static SOFT_TMR s_tTmr[TMR_COUNT] = {0};

/* 这2个全局变量转用于 bsp_DelayMS() 函数 */
static volatile uint32_t s_uiDelayCount = 0;
static volatile uint8_t s_ucTimeOutFlag = 0;

static __IO uint8_t g_ucEnableSystickISR = 0; /* 等待变量初始化 */

/*
	全局运行时间，单位1ms
	最长可以表示 24.85天，如果你的产品连续运行时间超过这个数，则必须考虑溢出问题
*/
__IO int32_t g_iRunTime = 0;

static void bsp_SoftTimerDec(SOFT_TMR *_tmr);

/**
 * @brief 配置systick中断，并初始化软件定时器变量
 *
 */
void bsp_InitTimer(void)
{
	uint8_t i;

	/* 清零所有的软件定时器 */
	for (i = 0; i < TMR_COUNT; i++)
	{
		s_tTmr[i].Count = 0;
		s_tTmr[i].PreLoad = 0;
		s_tTmr[i].Flag = 0;
		s_tTmr[i].Mode = TMR_ONCE_MODE; /* 缺省是1次性工作模式 */
	}

	/* 配置systick 中断为1ms*/
	/* setup systick timer for 1000Hz interrupts */
	if (SysTick_Config(SystemCoreClock / 1000U))
	{
		/* capture error */
		while (1)
		{
		}
	}
	/* configure the systick handler priority */
	NVIC_SetPriority(SysTick_IRQn, 0x00U);

	g_ucEnableSystickISR = 1; /* 1表示执行systick中断 */

	// 硬件定时器初始化
	// bsp_InitHardTimer();
}

extern void bsp_RunPer1ms(void);
extern void bsp_RunPer10ms(void);
/**
 * @brief SysTick中断服务程序，每隔1ms进入1次
 *
 */
void SysTick_ISR(void)
{
	static uint8_t s_count = 0;
	uint8_t i;

	/* 每隔1ms进来1次 （仅用于 bsp_DelayMS） */
	if (s_uiDelayCount > 0)
	{
		if (--s_uiDelayCount == 0)
		{
			s_ucTimeOutFlag = 1;
		}
	}

	/* 每隔1ms，对软件定时器的计数器进行减一操作 */
	for (i = 0; i < TMR_COUNT; i++)
	{
		bsp_SoftTimerDec(&s_tTmr[i]);
	}

	/* 全局运行时间每1ms增1 */
	g_iRunTime++;
	if (g_iRunTime == 0x7FFFFFFF) /* 这个变量是 int32_t 类型，最大数为 0x7FFFFFFF */
	{
		g_iRunTime = 0;
	}

	bsp_RunPer1ms(); /* 每隔1ms调用一次此函数，此函数在 bsp.c */

	if (++s_count >= 10)
	{
		s_count = 0;

		bsp_RunPer10ms(); /* 每隔10ms调用一次此函数，此函数在 bsp.c */
	}
}

/**
 * @brief 系统嘀嗒定时器中断服务程序 1ms的中断频率
 *
 */
void SysTick_Handler(void)
{
	if (g_ucEnableSystickISR == 0)
	{
		return;
	}

	SysTick_ISR();	/* 自己的滴答定时中断服务程序 */
}

/**
 * @brief 每隔1ms对所有定时器变量减1。必须被SysTick_ISR周期性调用。
 *
 * @param _tmr 定时器变量指针
 */
static void bsp_SoftTimerDec(SOFT_TMR *_tmr)
{
	if (_tmr->Count > 0)
	{
		/* 如果定时器变量减到1则设置定时器到达标志 */
		if (--_tmr->Count == 0)
		{
			_tmr->Flag = 1;

			/* 如果是自动模式，则自动重装计数器 */
			if (_tmr->Mode == TMR_AUTO_MODE)
			{
				_tmr->Count = _tmr->PreLoad;
			}
		}
	}
}

/**
 * @brief  ms级延迟，延迟精度为正负1ms
 *
 * @param n
 */
void bsp_DelayMS(uint32_t n)
{
	if (n == 0)
	{
		return;
	}
	else if (n == 1)
	{
		n = 2;
	}

	DISABLE_INT(); /* 关中断 */

	s_uiDelayCount = n;
	s_ucTimeOutFlag = 0;

	ENABLE_INT(); /* 开中断 */

	while (1)
	{
		bsp_Idle(); /* CPU空闲执行的操作， 见 bsp.c 和 bsp.h 文件 */

		/*
			等待延迟时间到
			注意：编译器认为 s_ucTimeOutFlag = 0，所以可能优化错误，因此 s_ucTimeOutFlag 变量必须申明为 volatile
		*/
		if (s_ucTimeOutFlag == 1)
		{
			break;
		}
	}
}

/**
 * @brief us级延迟。 必须在systick定时器启动后才能调用此函数。
 *
 * @param n
 */
void bsp_DelayUS(uint32_t n)
{
	uint32_t ticks;
	uint32_t told;
	uint32_t tnow;
	uint32_t tcnt = 0;
	uint32_t reload;

	reload = SysTick->LOAD;
	ticks = n * (SystemCoreClock / 1000000); /* 需要的节拍数 */

	tcnt = 0;
	told = SysTick->VAL; /* 刚进入时的计数器值 */

	while (1)
	{
		tnow = SysTick->VAL;
		if (tnow != told)
		{
			/* SYSTICK是一个递减的计数器 */
			if (tnow < told)
			{
				tcnt += told - tnow;
			}
			/* 重新装载递减 */
			else
			{
				tcnt += reload - tnow + told;
			}
			told = tnow;

			/* 时间超过/等于要延迟的时间,则退出 */
			if (tcnt >= ticks)
			{
				break;
			}
		}
	}
}

/**
 * @brief 启动一个定时器，并设置定时周期。
 *
 * @param _id 定时器ID，值域【0,TMR_COUNT-1】。用户必须自行维护定时器ID，以避免定时器ID冲突。
 * @param _period 定时周期，单位1ms
 */
void bsp_StartTimer(uint8_t _id, uint32_t _period)
{
	if (_id >= TMR_COUNT)
	{
		/* 打印出错的源代码文件名、函数名称 */
		BSP_Printf("Error: file %s, function %s()\r\n", __FILE__, __FUNCTION__);
		while (1)
			; /* 参数异常，死机等待看门狗复位 */
	}

	DISABLE_INT(); /* 关中断 */

	s_tTmr[_id].Count = _period;	  /* 实时计数器初值 */
	s_tTmr[_id].PreLoad = _period;	  /* 计数器自动重装值，仅自动模式起作用 */
	s_tTmr[_id].Flag = 0;			  /* 定时时间到标志 */
	s_tTmr[_id].Mode = TMR_ONCE_MODE; /* 1次性工作模式 */

	ENABLE_INT(); /* 开中断 */
}

/**
 * @brief 启动一个自动定时器，并设置定时周期。
 *
 * @param _id 定时器ID，值域【0,TMR_COUNT-1】。用户必须自行维护定时器ID，以避免定时器ID冲突。
 * @param _period  定时周期，单位10ms
 */
void bsp_StartAutoTimer(uint8_t _id, uint32_t _period)
{
	if (_id >= TMR_COUNT)
	{
		/* 打印出错的源代码文件名、函数名称 */
		BSP_Printf("Error: file %s, function %s()\r\n", __FILE__, __FUNCTION__);
		while (1)
			; /* 参数异常，死机等待看门狗复位 */
	}

	DISABLE_INT(); /* 关中断 */

	s_tTmr[_id].Count = _period;	  /* 实时计数器初值 */
	s_tTmr[_id].PreLoad = _period;	  /* 计数器自动重装值，仅自动模式起作用 */
	s_tTmr[_id].Flag = 0;			  /* 定时时间到标志 */
	s_tTmr[_id].Mode = TMR_AUTO_MODE; /* 自动工作模式 */

	ENABLE_INT(); /* 开中断 */
}

/**
 * @brief 停止一个定时器
 *
 * @param _id 	_id     : 定时器ID，值域【0,TMR_COUNT-1】。用户必须自行维护定时器ID，以避免定时器ID冲突。
 */
void bsp_StopTimer(uint8_t _id)
{
	if (_id >= TMR_COUNT)
	{
		/* 打印出错的源代码文件名、函数名称 */
		BSP_Printf("Error: file %s, function %s()\r\n", __FILE__, __FUNCTION__);
		while (1)
			; /* 参数异常，死机等待看门狗复位 */
	}

	DISABLE_INT(); /* 关中断 */

	s_tTmr[_id].Count = 0;			  /* 实时计数器初值 */
	s_tTmr[_id].Flag = 0;			  /* 定时时间到标志 */
	s_tTmr[_id].Mode = TMR_ONCE_MODE; /* 自动工作模式 */

	ENABLE_INT(); /* 开中断 */
}

/**
 * @brief  检测定时器是否超时
 *
 * @param _id 定时器ID，值域【0,TMR_COUNT-1】。用户必须自行维护定时器ID，以避免定时器ID冲突。 定时周期，单位1ms
 * @return uint8_t 返回 0 表示定时未到， 1表示定时到
 */
uint8_t bsp_CheckTimer(uint8_t _id)
{
	if (_id >= TMR_COUNT)
	{
		return 0;
	}

	if (s_tTmr[_id].Flag == 1)
	{
		s_tTmr[_id].Flag = 0;
		return 1;
	}
	else
	{
		return 0;
	}
}

/**
 * @brief 取CPU运行时间，单位1ms。最长可以表示 24.85天，如果你的产品连续运行时间超过这个数，则必须考虑溢出问题
 *
 * @return int32_t CPU运行时间，单位1ms
 */
int32_t bsp_GetRunTime(void)
{
	int32_t runtime;

	DISABLE_INT(); /* 关中断 */

	runtime = g_iRunTime; /* 这个变量在Systick中断中被改写，因此需要关中断进行保护 */

	ENABLE_INT(); /* 开中断 */

	return runtime;
}

/**
 * @brief 计算当前运行时间和给定时刻之间的差值。处理了计数器循环。
 *
 * @param _LastTime 上个时刻
 * @return int32_t 当前时间和过去时间的差值，单位1ms
 */
int32_t bsp_CheckRunTime(int32_t _LastTime)
{
	int32_t now_time;
	int32_t time_diff;

	DISABLE_INT(); /* 关中断 */

	now_time = g_iRunTime; /* 这个变量在Systick中断中被改写，因此需要关中断进行保护 */

	ENABLE_INT(); /* 开中断 */

	if (now_time >= _LastTime)
	{
		time_diff = now_time - _LastTime;
	}
	else
	{
		time_diff = 0x7FFFFFFF - _LastTime + now_time;
	}

	return time_diff;
}

/*
*********************************************************************************************************
*	下面的代码使用一个TIM的4个捕获中断，实现4个硬件定时器
*********************************************************************************************************
*/

#ifdef TIM_HARD

/*
*********************************************************************************************************
*	函 数 名: bsp_InitHardTimer
*	功能说明: 配置 TIMx，用于us级别硬件定时。TIMx将自由运行，永不停止.
*			TIMx可以用TIM2 - TIM5 之间的TIM, 这些TIM有4个通道, 挂在 APB1 上，输入时钟=SystemCoreClock / 2
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitHardTimer(void)
{
	TIM_HandleTypeDef TimHandle = {0};
	uint32_t usPeriod;
	uint16_t usPrescaler;
	uint32_t uiTIMxCLK;
	TIM_TypeDef *TIMx = TIM_HARD;

	RCC_TIM_HARD_CLK_ENABLE(); /* 使能TIM时钟 */

	/*-----------------------------------------------------------------------
		bsp.c 文件中 void SystemClock_Config(void) 函数对时钟的配置如下:

		System Clock source       = PLL (HSE)
		SYSCLK(Hz)                = 400000000 (CPU Clock)
		HCLK(Hz)                  = 200000000 (AXI and AHBs Clock)
		AHB Prescaler             = 2
		D1 APB3 Prescaler         = 2 (APB3 Clock  100MHz)
		D2 APB1 Prescaler         = 2 (APB1 Clock  100MHz)
		D2 APB2 Prescaler         = 2 (APB2 Clock  100MHz)
		D3 APB4 Prescaler         = 2 (APB4 Clock  100MHz)

		因为APB1 prescaler != 1, 所以 APB1上的TIMxCLK = APB1 x 2 = 200MHz;
		因为APB2 prescaler != 1, 所以 APB2上的TIMxCLK = APB2 x 2 = 200MHz;
		APB4上面的TIMxCLK没有分频，所以就是100MHz;

		APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14，LPTIM1
		APB2 定时器有 TIM1, TIM8 , TIM15, TIM16，TIM17

		APB4 定时器有 LPTIM2，LPTIM3，LPTIM4，LPTIM5

	----------------------------------------------------------------------- */
	if ((TIMx == TIM1) || (TIMx == TIM8) || (TIMx == TIM15) || (TIMx == TIM16) || (TIMx == TIM17))
	{
		/* APB2 定时器时钟 = 200M */
		uiTIMxCLK = SystemCoreClock / 2;
	}
	else
	{
		/* APB1 定时器 = 200M */
		uiTIMxCLK = SystemCoreClock / 2;
	}

	usPrescaler = uiTIMxCLK / 1000000 - 1; /* 分频比 = 1 */

	if (TIMx == TIM2 || TIMx == TIM5)
	{
		usPeriod = 0xFFFFFFFF;
	}
	else
	{
		usPeriod = 0xFFFF;
	}

	/*
	   设置分频为usPrescaler后，那么定时器计数器计1次就是1us
	   而参数usPeriod的值是决定了最大计数：
	   usPeriod = 0xFFFF 表示最大0xFFFF微秒。
	   usPeriod = 0xFFFFFFFF 表示最大0xFFFFFFFF微秒。
	*/
	TimHandle.Instance = TIMx;
	TimHandle.Init.Prescaler = usPrescaler;
	TimHandle.Init.Period = usPeriod;
	TimHandle.Init.ClockDivision = 0;
	TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
	TimHandle.Init.RepetitionCounter = 0;
	TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;

	if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
	{
		Error_Handler(__FILE__, __LINE__);
	}

	/* 配置定时器中断，给CC捕获比较中断使用 */
	{
		HAL_NVIC_SetPriority(TIM_HARD_IRQn, 0, 2);
		HAL_NVIC_EnableIRQ(TIM_HARD_IRQn);
	}

	/* 启动定时器 */
	HAL_TIM_Base_Start(&TimHandle);
}

/*
*********************************************************************************************************
*	函 数 名: bsp_StartHardTimer
*	功能说明: 使用TIM2-5做单次定时器使用, 定时时间到后执行回调函数。可以同时启动4个定时器通道，互不干扰。
*             定时精度正负1us （主要耗费在调用本函数的执行时间）
*			  TIM2和TIM5 是32位定时器。定时范围很大
*			  TIM3和TIM4 是16位定时器。
*	形    参: _CC : 捕获比较通道几，1，2，3, 4
*             _uiTimeOut : 超时时间, 单位 1us. 对于16位定时器，最大 65.5ms; 对于32位定时器，最大 4294秒
*             _pCallBack : 定时时间到后，被执行的函数
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_StartHardTimer(uint8_t _CC, uint32_t _uiTimeOut, void *_pCallBack)
{
	uint32_t cnt_now;
	uint32_t cnt_tar;
	TIM_TypeDef *TIMx = TIM_HARD;

	/* H743速度较快，无需补偿延迟，实测精度正负1us */

	cnt_now = TIMx->CNT;
	cnt_tar = cnt_now + _uiTimeOut; /* 计算捕获的计数器值 */
	if (_CC == 1)
	{
		s_TIM_CallBack1 = (void (*)(void))_pCallBack;

		TIMx->CCR1 = cnt_tar;			  /* 设置捕获比较计数器CC1 */
		TIMx->SR = (uint16_t)~TIM_IT_CC1; /* 清除CC1中断标志 */
		TIMx->DIER |= TIM_IT_CC1;		  /* 使能CC1中断 */
	}
	else if (_CC == 2)
	{
		s_TIM_CallBack2 = (void (*)(void))_pCallBack;

		TIMx->CCR2 = cnt_tar;			  /* 设置捕获比较计数器CC2 */
		TIMx->SR = (uint16_t)~TIM_IT_CC2; /* 清除CC2中断标志 */
		TIMx->DIER |= TIM_IT_CC2;		  /* 使能CC2中断 */
	}
	else if (_CC == 3)
	{
		s_TIM_CallBack3 = (void (*)(void))_pCallBack;

		TIMx->CCR3 = cnt_tar;			  /* 设置捕获比较计数器CC3 */
		TIMx->SR = (uint16_t)~TIM_IT_CC3; /* 清除CC3中断标志 */
		TIMx->DIER |= TIM_IT_CC3;		  /* 使能CC3中断 */
	}
	else if (_CC == 4)
	{
		s_TIM_CallBack4 = (void (*)(void))_pCallBack;

		TIMx->CCR4 = cnt_tar;			  /* 设置捕获比较计数器CC4 */
		TIMx->SR = (uint16_t)~TIM_IT_CC4; /* 清除CC4中断标志 */
		TIMx->DIER |= TIM_IT_CC4;		  /* 使能CC4中断 */
	}
	else
	{
		return;
	}
}

/*
*********************************************************************************************************
*	函 数 名: TIMx_IRQHandler
*	功能说明: TIM 中断服务程序
*	形    参：无
*	返 回 值: 无
*********************************************************************************************************
*/
void TIM_HARD_IRQHandler(void)
{
	uint16_t itstatus = 0x0, itenable = 0x0;
	TIM_TypeDef *TIMx = TIM_HARD;

	itstatus = TIMx->SR & TIM_IT_CC1;
	itenable = TIMx->DIER & TIM_IT_CC1;

	if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
	{
		TIMx->SR = (uint16_t)~TIM_IT_CC1;
		TIMx->DIER &= (uint16_t)~TIM_IT_CC1; /* 禁能CC1中断 */

		/* 先关闭中断，再执行回调函数。因为回调函数可能需要重启定时器 */
		s_TIM_CallBack1();
	}

	itstatus = TIMx->SR & TIM_IT_CC2;
	itenable = TIMx->DIER & TIM_IT_CC2;
	if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
	{
		TIMx->SR = (uint16_t)~TIM_IT_CC2;
		TIMx->DIER &= (uint16_t)~TIM_IT_CC2; /* 禁能CC2中断 */

		/* 先关闭中断，再执行回调函数。因为回调函数可能需要重启定时器 */
		s_TIM_CallBack2();
	}

	itstatus = TIMx->SR & TIM_IT_CC3;
	itenable = TIMx->DIER & TIM_IT_CC3;
	if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
	{
		TIMx->SR = (uint16_t)~TIM_IT_CC3;
		TIMx->DIER &= (uint16_t)~TIM_IT_CC3; /* 禁能CC2中断 */

		/* 先关闭中断，再执行回调函数。因为回调函数可能需要重启定时器 */
		s_TIM_CallBack3();
	}

	itstatus = TIMx->SR & TIM_IT_CC4;
	itenable = TIMx->DIER & TIM_IT_CC4;
	if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
	{
		TIMx->SR = (uint16_t)~TIM_IT_CC4;
		TIMx->DIER &= (uint16_t)~TIM_IT_CC4; /* 禁能CC4中断 */

		/* 先关闭中断，再执行回调函数。因为回调函数可能需要重启定时器 */
		s_TIM_CallBack4();
	}
}

#endif
